#!/usr/bin/python
# -*- encoding: latin1 -*-
#
# Copyright 2007 Scott Kirkwood

"""Convert a Memory Map File into Meeting Notes.

Note this is essentially a copy paste of code.google.com/p/mm2notes.py.
This code should contain only what's required for parsing a FreeMind mm file.
"""

__author__ = 'scottkirkwood@google.com (Scott Kirkwood)'

import _elementtree
import cgi
import codecs
import re
import sys
import time

class Mm2Notes:
    def __init__(self):
        self.et_in = None
        self.as_html = True
        self.title_text = ''
        self.full_html = True
        self.order_by_time = False

    def set_order_by_time(self, order_by_time):
        self.order_by_time = order_by_time
        
    def open(self, infilename):
        """ Open the .mm file and create a notes as a list of lines """
        text = file(infilename).read()
        return self.parse_text(text)

    def parse_text(self, text):
        self.xmlparse(text)
        lines = self.convert_to_html(self.convert())
        return lines

    def write(self, outfile, lines):
        """ Write out the lines, written as a convenience function."""
        outfile.write(u'\n'.join(lines))
        outfile.write(u'\n')
        
    def xmlparse(self, text):
        """ import the XML text into self.et_in."""
        self.et_in = _elementtree.XML(text)
        return self.et_in
      
    def convert(self):
        """ Convert self.et_in to a HTML as a list of lines in S5 format """

        topic = []
        attendees = []
        emails = []
        discussed = []
        actionitems = []
        self.title_text = self.et_in.find('node').attrib['TEXT']

        presentation = self.et_in.find('node')

        for node in presentation.findall('node'):
            node_name = node.attrib['TEXT']
            if self._starts_with(node_name, ['attendee', 'people']):
                attendees, emails = self._handle_attendees(node)
            elif self._starts_with(node_name, ['topic', 'subject']):
                topic = self._handle_topic(node)
            elif self._starts_with(node_name, ['discus', 'minutes', 
                    'meeting', 'notes']):
                discussed = self._handle_discussed(node)
            elif self._starts_with(node_name, ['action', 'a.i', 'ai']):
                actionitems = self._handle_action_items(node)
        
        return {'title': self._handle_title(),
                'topic': topic,
                'attendees': attendees, 
                'emails': emails,
                'actionitems': actionitems,
                'discussed': discussed}
    
    def convert_to_html(self, info):
        return self._html_wrapper(self._html_title(info['title']) + 
                                  self._html_topic(info['topic']) + 
                                  self._html_attendees(info['attendees']) + 
                                  self._html_action_items(info['actionitems']) + 
                                  self._html_discussed(info['discussed']))
            
    def _starts_with(self, name, list):
        for item in list:
            if name.lower().startswith(item):
                return True
        return False
        
    def _html_wrapper(self, list):
        head = [
              '<?xml version="1.0" encoding="UTF-8"?>',
          '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ',
          '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
          '<html xmlns="http://www.w3.org/1999/xhtml">',
          '<head>',
          '<title>%s</title>' % (self.title_text), 
          '</head>',
          '<body>',
          ]
        tail = [
              '</body>',
          '</html>'
          ]
        if self.full_html:
            return head + list + tail
        else:
            return list
      
    def _handle_attendees(self, node):
        topnodes = node.findall('node')
        # find the maximum depth in the attendee lists           
      
        mxd = self._max_depth(topnodes)
        if mxd >= 3:
            # assume entity / attendee / email and ignore anything else
            names = self._three_level_attendees(node)
        elif mxd == 2:
            # assume attendee / email
            names = self._two_level_attendees(node)
        elif mxd == 1:
            names = [x.attrib['TEXT'] for x in topnodes]
        else:
            # no names
            names = []

        re_email = re.compile(r'[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]{4,}',
                re.IGNORECASE)
        emails = []
        for name in names:
            grps = re_email.search(name)
            if grps:
                emails.append(grps.group(0))
        return names, emails

    def _html_attendees(self, attendees):
        if not attendees:
            return []
        return [self.open_close('b', 'Attendees: ') + ', '.join(attendees) + self._nl()]
    
    def _max_depth(self, nodes):
        """Returns the maximum depth of tree for the supplied list of nodes."""
        if not nodes:
            # empty list, zero depth
            return 0
        else:
            depths = [self._max_depth(x.findall('node')) for x in nodes]
            return 1 + max(depths)

    def _two_level_attendees(self, node):
        """ Top node is the person's name, 
        If there's a sub node, it's the email, or something like that
        """
        names = []
        for line in node.findall('node'):
            fullname = line.attrib['TEXT']
            emailnodes = line.findall('node')
            
            if emailnodes:
                email = emailnodes[0].attrib['TEXT']
            else:
                email = ''
            if len(email) > 0:
                names.append('%s (%s)' % (fullname, email))
            else:
                names.append('%s' % (fullname))
        
        return names
    
    def _three_level_attendees(self, node):
        """Here the top node is the location, 
        the child nodes are the people
        """
        
        locations = []
        for location in node.findall('node'):
            loc_name = location.attrib['TEXT']
            names = self._two_level_attendees(location)
            locations.append("%s [%s]" % (loc_name, ', '.join(names)))
            
        return locations

    def _handle_topic(self, node):
        text = []
        for line in node.findall('node'):
            text.append(line.attrib['TEXT'])
        
        return text

    def _html_topic(self, topic):
        if not topic:
            return []
        return [self.open_close('b', 'Topic: ') + ' '.join(topic) + self._nl()]
    
    def _nl(self):
        if self.as_html:
            return '<br/>\n'
        return '\n'
    
    def _handle_discussed(self, node):
        people_time = []
        for sub in node.findall('node'):
            name = sub.attrib['TEXT']
            for subsub in sub.findall('node'):
                cur = []
                created_time = int(subsub.attrib['CREATED'])
                self._nest_text(subsub, cur)
                if cur:
                    text = '\n'.join(cur)
                else:
                    text = ''
                if text:
                    people_time.append((created_time, name, text))

        if self.order_by_time:
            people_time.sort()
        return people_time

    def _html_discussed(self, people_time):
        if not people_time:
            return []
        self.start_time = people_time[0][0]
        ret = [self.title("Meeting Minutes")]
        self._open_tag('ul', ret)
        last_name = None
        for curtime, name, text in people_time:
            if name != last_name:
                last_name = name
                if len(ret) > 2:
                    self.close_tag('ul', ret)
                    self.close_tag('li', ret)
                self._open_tag('li', ret)
                if self.order_by_time:
                    ret.append(self.show_user_time(name, curtime))
                else:
                    ret.append(name)
                self._open_tag('ul', ret)
                
            self._open_tag('li', ret)
            ret.append(text)
            self.close_tag('li', ret)
            
        self.close_tag('ul', ret)
        self.close_tag('li', ret)
        self.close_tag('ul', ret)
    
        return ret
        
    def _handle_title(self):
        return "%s" %  (self.title_text)

    def _html_title(self, title):
        if self.as_html:
            return ["<h3>%s</h3>" % (title)]
        return title

    def _handle_action_items(self, node):
        ret = []
        for sub in node.findall('node'):
            self._nest_text(sub, ret)
       
        return ret
    
    def _html_action_items(self, ais):
        ret = []
        if not ais:
            return ret
        ret.append(self.title("Action Items"))
        
        self._open_tag('ul', ret)
        for sub in ais:
            self._open_tag('li', ret)
            self._html_nest_text(sub, ret)
            self.close_tag('li', ret)
        
        self.close_tag('ul', ret)
        return ret
        
    def show_user_time(self, name, curtime):
        return '%s (%s)' % (name, self.format_time(curtime))

    def _nest_text(self, node, ret):
        txt = self.escape(node.attrib['TEXT'])
        sublist = []
        subnodes = node.findall('node')
        if len(subnodes) > 0:
            ret.append([txt, sublist])
            for sub in node.findall('node'):
                self._nest_text(sub, sublist)
        else:
            ret.append(txt)
        
    def _html_nest_text(self, node, ret):
        ret.append(node[0])
        subnodes = node[1:]
        if len(subnodes) > 0:
            self._open_tag('ul', ret)
            for sub in subnodes:
                self._open_tag('li', ret)
                self._html_nest_text(sub, ret)
                self.close_tag('li', ret)
        
            self.close_tag('ul', ret)

    def title(self, name):
        if self.as_html:
            return '<b>%s</b><br/>' % (name)
        return name

    def _open_tag(self, tag, lst):
        if self.as_html:
            lst.append('<%s>' % tag)

    def close_tag(self, tag, lst):
        if self.as_html:
            lst.append('</%s>' % tag)

    def open_close(self, tag, text, lst=None):
        if lst == None:
            return self._open_close(tag, text)
        lst.append(self._open_close(tag, text))

    def _open_close(self, tag, text):
        if self.as_html:
            return '<%s>%s</%s>' % (tag, cgi.escape(text), tag)
        else:
            return text

    def escape(self, text):
        if self.as_html:
            return cgi.escape(text)
        return text

    def format_time(self, curtime):
        timesecs = (curtime - self.start_time) / (1000)
        
        minutes = timesecs // 60
        sec = timesecs % 60
        
        if minutes == 0 and sec == 0:
            t = time.localtime(self.start_time / 1000.0)
            tzn = time.tzname[0]
            return '%02d:%02d:%02d %s'  % (t[3], t[4], t[5], tzn)
        if minutes == 0:
            return '%d secs' % (sec)
        if sec == 0:
            return '%d min' % (minutes)
        return '%d min %d sec' % (minutes, sec)
        
